/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*
 * @version bsp.comoro.Comoro v 1.0.0
 * @author Dr.NP <np@bsgroup.org>
 * @package BS.Play
 *
 * Comoro actionscript3 connector
 */

package bsp.comoro
{
	import bsp.utils.Debug;
	
	import flash.events.*;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	
	public class ComoroBase
	{
		// Objects
		private var o						: Socket = new Socket();
		private var e						: Encode = new Encode();
		private var p						: Package = new Package();
		private var s						: Sequence = new Sequence();
		
		// Handlers
		private var f_connect			: Function;
		private var b_connect			: Boolean = false;
		private var f_close				: Function;
		private var b_close				: Boolean = false;
		private var f_error				: Function;
		private var b_error				: Boolean = false;
		private var f_security			: Function;
		private var b_security			: Boolean = false;
		private var f_recv				: Function;
		private var b_recv				: Boolean = false;
		private var f_send				: Function;
		private var b_send				: Boolean = false;
		private var f_hb_s				: Function;
		private var b_hb_s				: Boolean = false;
		private var f_hb_r				: Function;
		private var b_hb_r				: Boolean = false;
		private var f_login				: Function;
		private var b_login				: Boolean = false;
		private var f_srv_list			: Function;
		private var b_srv_list			: Boolean = false;
		private var f_select_server		: Function;
		private var b_select_server		: Boolean = false;
		private var f_validated			: Function;
		private var b_validated			: Boolean = false;
		
		// Timer
		private var hb_interval_val		: uint = 2000;
		private var hb_timer				: Timer;
		private var flush_timer			: Timer;
		
		// Runner
		private var runner_id			: int;
		private var username				: String;
		private var password				: String;
		private var session				: ByteArray;
		
		// Server list
		private var srv_list				: Array;
		
		/* Contructor */
		public function ComoroBase(rid : int)
		{
			// Socket object
			this.o.addEventListener(Event.CONNECT, this._on_connect);
			this.o.addEventListener(Event.CLOSE, this._on_close);
			this.o.addEventListener(IOErrorEvent.IO_ERROR, this._on_io_error);
			this.o.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this._on_security_error);
			this.o.addEventListener(ProgressEvent.SOCKET_DATA, this._on_recv);
			
			this.runner_id = rid;
			this.session = new ByteArray();
		}
		
		/* Socket connect */
		public function connect(addr : String, port : Number) : Boolean
		{
			if (this.o.connected)
			{
				return false;
			}
			
			this.o.connect(addr, port);
			
			return true;
		}
		
		/* Socket disconnect */
		public function disconnect() : Boolean
		{
			if (!this.o.connected)
			{
				return false;
			}
			
			this.o.close();
			
			if (this.hb_timer)
			{
				this.hb_timer.stop();
			}
			
			if (this.b_close)
			{
				this.f_close();
			}
			
			return true;
		}
		
		/* Login process */
		public function login(u : String, p : String) : Boolean
		{
			var d : ByteArray = new ByteArray();
			var i : uint;
			
			if (!this.o.connected)
			{
				return false;
			}
			
			// Login signal
			d.writeUnsignedInt(0xFFFFFFFB);
			
			if (u.length > 64)
			{
				// Cut off length
				u = u.substr(0, 64);
			}
			
			if (p.length > 64)
			{
				// Cut off length
				p = p.substr(0, 64);
			}
			
			d.writeUTFBytes(u);
			for (i = 0; i < 64 - u.length; i ++)
			{
				d.writeByte(0x0);
			}
			
			d.writeUTFBytes(p);
			for (i = 0; i < 64 - p.length; i ++)
			{
				d.writeByte(0x0);
			}
			
			d.writeUnsignedInt(this.runner_id);
			
			this.username = u;
			this.password = p;
			
			i = this.osend(d);
			
			return true;
		}
		
		/* Get server status list */
		public function get_server_list() : Boolean
		{
			if (!this.o.connected)
			{
				return false;
			}
			
			this.o.writeUnsignedInt(0xFFFFFFFA);
			this.o.writeUnsignedInt(0x0);
			this.o.flush();
			
			return true;
		}
		
		/* Select server */
		public function select_server(srv_id : uint) : Boolean
		{
			if (!this.o.connected)
			{
				return false;
			}
			
			this.o.writeUnsignedInt(0xFFFFFFF5);
			this.o.writeUnsignedInt(srv_id);
			this.o.flush();
			
			return true;
		}
		
		/* Send session data to base server */
		public function validate_session() : Boolean
		{
			if (!this.o.connected)
			{
				return false;
			}
			
			this.o.writeUnsignedInt(0xFFFFFFF4);
			this.o.writeUnsignedInt(this.runner_id);
			this.o.writeBytes(this.session);
			this.o.flush();
			
			return true;
		}
		
		/* * * * * * Sends * * * * * */
		public function psend(cmd_id : uint, p : Array) : uint
		{
			if (cmd_id <= 0)
			{
				return 0;
			}
			
			var tmp : ByteArray = this.s.add(this.e.encode(this.p.pack(cmd_id, p)));
			var n_sent : uint = tmp.length;
			
			this.o.writeUnsignedInt(tmp.length + 4);
			this.o.writeBytes(tmp);
			this.o.flush();
			
			return n_sent;
		}
		
		public function osend(d : ByteArray) : uint
		{
			if (!this.o.connected)
			{
				return 0;
			}
			
			this.o.writeBytes(d);
			this.o.flush();
			
			return d.length
		}
		
		/* * * * * * Sets * * * * * */
		public function set app_id(l : uint) : void
		{
			if (l > 0)
			{
				this.runner_id = l;
			}
			
			return;
		}
		
		public function set hb_interval(l : uint) : void
		{
			if (l > 0)
			{
				this.hb_interval_val = l;
			}
			
			return;
		}
		
		public function set m_connect(l : Function) : void
		{
			this.f_connect = l;
			this.b_connect = true;
			
			return;
		}
		
		public function set m_close(l : Function) : void
		{
			this.f_close = l;
			this.b_close = true;
			
			return;
		}
		
		public function set m_error(l : Function) : void
		{
			this.f_error = l;
			this.b_error = true;
			
			return;
		}
		
		public function set m_security(l : Function) : void
		{
			this.f_security = l;
			this.b_security = true;
			
			return;
		}
		
		public function set m_recv(l : Function) : void
		{
			this.f_recv = l;
			this.b_recv = true;
			
			return;
		}
		
		public function set m_send(l : Function) : void
		{
			this.f_send = l;
			this.b_send = true;
			
			return;
		}
		
		public function set m_hb_s(l : Function) : void
		{
			this.f_hb_s = l;
			this.b_hb_s = true;
			
			return;
		}
		
		public function set m_hb_r(l : Function) : void
		{
			this.f_hb_r = l;
			this.b_hb_r = true;
			
			return;
		}
		
		public function set m_login(l : Function) : void
		{
			this.f_login = l;
			this.b_login = true;
			
			return;
		}
		
		public function set m_srv_list(l : Function) : void
		{
			this.f_srv_list = l;
			this.b_srv_list = true;
			
			return;
		}
		
		public function set m_select_server(l : Function) : void
		{
			this.f_select_server = l;
			this.b_select_server = true;
			
			return;
		}
		
		public function set m_validated(l : Function) : void
		{
			this.f_validated = l;
			this.b_validated = true;
			
			return;
		}
		
		/* * * * * * Events * * * * * */
		private function _on_connect(e : Event) : void
		{
			// Connected, start timer
			if (!this.hb_timer)
			{
				this.hb_timer = new Timer(this.hb_interval_val);
			}
			
			if (!this.hb_timer.hasEventListener(TimerEvent.TIMER))
			{
				this.hb_timer.addEventListener(TimerEvent.TIMER, this._hb);
			}
			
			this.hb_timer.start();
			
			// Check sequence buffer every 6 secs
			if (!this.flush_timer)
			{
				this.flush_timer = new Timer(6000);
			}
			
			if (!this.flush_timer.hasEventListener(TimerEvent.TIMER))
			{
				this.flush_timer.addEventListener(TimerEvent.TIMER, this._flush);
			}
			
			this.flush_timer.start();
			this.s.empty();
			
			// Register runner into server
			//this._rg(this.runner_id);
			
			if (this.b_connect)
			{
				this.f_connect();
			}
			
			this.srv_list = new Array();
			
			return;
		}
		
		private function _on_close(e : Event) : void
		{
			// Disconnected, stop timer
			if (this.hb_timer)
			{
				this.hb_timer.stop();
			}
			
			if (this.flush_timer)
			{
				this.flush_timer.stop();
			}
			
			if (this.b_close)
			{
				this.f_close();
			}
			
			return;
			
			
		}
		
		private function _on_io_error(e : Event) : void
		{
			if (this.b_error)
			{
				this.f_error();
			}
			
			return;
		}
		
		private function _on_security_error(e : Event) : void
		{
			if (this.o.connected)
			{
				// Connected ??? -_-!!!
				this.disconnect();
			}
			
			if (this.b_security)
			{
				this.f_security();
			}
			
			return;
		}
		
		private function _on_recv(e : Event) : void
		{
			if (this.b_recv)
			{
				var r : Array;
				var len : uint;
				var seq_id : uint;
				var res : uint;
				var tmp : ByteArray = new ByteArray();
				var edata : ByteArray = new ByteArray();
				
				while (this.o.connected && this.o.bytesAvailable >= 4)
				{
					len = this.o.readUnsignedInt();
					
					if (len == 0xffffffff)
					{
						// Heartbeat response
						res = this.o.readUnsignedInt();
						
						if (this.b_hb_r)
						{
							this.f_hb_r();
						}
					}
					
					else if (len == 0xfffffffe && this.o.bytesAvailable >= 4)
					{
						// Sequence response
						res = this.o.readUnsignedInt();
						
						this.s.del(res);
					}
					
					else if (len == 0xfffffffd && this.o.bytesAvailable >= 4)
					{
						// Sequence request
						res = this.o.readUnsignedInt();
						
						this.s.resend(res, this.o);
					}
					
					else if (len == 0xfffffffb && this.o.bytesAvailable >= 68)
					{
						// Login result
						res = this.o.readUnsignedInt();
						this.o.readBytes(this.session, 0, 64);
						
						if (this.b_login)
						{
							this.f_login(res);
						}
					}
					
					else if (len == 0xfffffffa && this.o.bytesAvailable >= 16)
					{
						// Base server list
						tmp.position = 0;
						this.o.readBytes(tmp, 0, 16);
						this._add_base_server(tmp);
					}
					
					else if (len == 0xfffffff5 && this.o.bytesAvailable >= 4)
					{
						// Select server signal
						res = this.o.readUnsignedInt();
						
						if (this.b_select_server)
						{
							this.f_select_server();
						}
					}
					
					else if (len == 0xfffffff4 && this.o.bytesAvailable >= 4)
					{
						// Validate result
						res = this.o.readUnsignedInt();
						
						if (this.b_validated)
						{
							this.f_validated(res);
						}
					}
					
					else if (len <= 0)
					{
						// Go out
					}
					
					else
					{
						edata = new ByteArray();
						seq_id = this.o.readUnsignedInt();
						
						if (seq_id > this.s.rcv_id + 1)
						{
							this.o.writeUnsignedInt(0xfffffffd)
							this.o.writeUnsignedInt(this.s.rcv_id + 1);
							this.o.flush();
						}
						
						this.o.readBytes(edata, 0, (len > this.o.bytesAvailable + 8) ? this.o.bytesAvailable : (len - 8));
						
						if (seq_id == this.s.rcv_id + 1)
						{
							tmp = this.e.decode(edata);
							this.f_recv(this.p.unpack(tmp));
							
							// Send response
							this.s.rcv_id = seq_id;
							this.o.writeUnsignedInt(0xfffffffe);
							this.o.writeUnsignedInt(seq_id);
							this.o.flush();
						}
					}
				}
			}
			
			return;
		}
		
		/* * * * * * Heartbeat * * * * * */
		private function _hb(e : TimerEvent) : void
		{
			if (this.o.connected)
			{
				this.o.writeUnsignedInt(0xffffffff);
				this.o.writeUnsignedInt(0x0);
				this.o.flush();
				
				if (this.b_hb_s)
				{
					this.f_hb_s();
				}
			}
			
			return;
		}
		
		/* * * * * * Runner register * * * * * */
		private function _rg(runner_id : int) : void
		{
			if (this.o.connected)
			{
				this.o.writeUnsignedInt(0xfffffff0);
				this.o.writeUnsignedInt(this.runner_id);
				this.o.flush();
			}
			
			return;
		}
		
		/* * * * * * Flush all data in sequence buffer * * * * * */
		private function _flush(e : TimerEvent) : void
		{
			if (this.o.connected)
			{
				this.s.flush(this.o);
			}
			
			return;
		}
		
		/* * * * * * Base server infor * * * * * */
		private function _add_base_server(v : ByteArray) : void
		{
			var base_id : uint;
			var srv_addr : uint;
			var srv_port : uint;
			var num_clients : uint;
			
			if (v.bytesAvailable < 16)
			{
				return;
			}
			
			base_id = v.readUnsignedInt();
			
			if (base_id == 0xFFFFFFFF)
			{
				v.readUnsignedInt();
				v.readUnsignedInt();
				v.readUnsignedInt();
				
				if (this.b_srv_list)
				{
					this.f_srv_list(this.srv_list);
				}
			}
			
			else
			{
				srv_addr = v.readUnsignedInt();
				srv_port = v.readUnsignedInt();
				num_clients = v.readUnsignedInt();
				
				this.srv_list[base_id] = new Array();
				this.srv_list[base_id]['srv_addr'] = srv_addr;
				this.srv_list[base_id]['srv_port'] = srv_port;
				this.srv_list[base_id]['num_clients'] = num_clients;
			}
			
			return;
		}
	}
}